The R package decorr is grounded on graph theory and spatial analysis. It offers concepts and functions to model Prehistorical iconographical composition and to prepare further analysis (clustering, Harris diagram, etc.) in order to contribute to cross-cultural iconography comparisons studies by a higher normalization of quantitative analysis (Alexander 2008; Huet and Alexander 2015; Huet 2018).

Decoration graphs

The main principle of decorr package is to considerate any iconographic compositions (here, ‘decorations’) as geometric graph of graphical units (GUs). This geometric graph is also known as planar graph or spatialised graph. The GUs are decorated surfaces (POLYGONS) modelised as nodes (POINTS) sharing proximity links (edges, LINES) one with another when their Voronoi cell share a border (birel: touches).  

GIS view of the Cerro Muriano 1 stelae with the Voronoi cells of its GUs, GUs (nodes) and proximity links between these GUs (edges)

GIS view of the Cerro Muriano 1 stelae with the Voronoi cells of its GUs, GUs (nodes) and proximity links between these GUs (edges)

 

Graph theory offers a conceptual framework and indices (global at the entire graph scale, local at the vertex scale) to deal with notions of networks, relationships and neighbourhoods. The graph is commonly built within a GIS interface. Indeed, use of GIS allows to consitute a spatial database of the decoration’s iconographic contents and facilitates data recording and visualization: snapping options for example to connect GUs (nodes) with lines (edges), features symbology, layer transparency, etc.  

The development version of decorr package and its vignette can be download from GitHub

devtools::install_github("zoometh/iconr", build_vignettes=TRUE)

The R package decorr is composed by functions and a dataset example. The main packages used by the decorr package are:

library(decorr)

Dataset

The training dataset are drawings with their listing and data for nodes, edges. The current path of training dataset is the exdata/ folder. All the input data (dataframes, shapefiles, images) start with a letter (regex "^[[:alpha:]]") while the output data start with a punctuation (regex "^[[:punct:]]") or a numerical (regex "^[[:digit:]]")

dataDir <- system.file("extdata", package = "decorr")
cat(grep("^[[:alpha:]]",list.files(dataDir),value=T), sep="\n")
> Brozas.Brozas.jpg
> Cerro_Muriano.Cerro_Muriano_1.jpg
> edges.csv
> edges.dbf
> edges.shp
> edges.shx
> edges.tsv
> gis.png
> Ibahernando.Ibahernando.jpg
> imgs.csv
> imgs.tsv
> nodes.csv
> nodes.dbf
> nodes.shp
> nodes.shx
> nodes.tsv
> Torrejon_Rubio.Torrejon_Rubio_1.jpg
> Zarza_de_Montanchez.Zarza_de_Montanchez.jpg

Each decoration is identified by its the name and the name of the site it belongs. Each decoration is a set of:

  • images (.jpg, .png, .jpeg, .tiff, .pdf, etc.)

  • decoration identifiers dataframe (.tsv or .csv)

imgs_path <- paste0(dataDir, "/imgs.csv")
imgs <- read.table(imgs_path, sep=";", stringsAsFactors = FALSE)
  • nodes and edges dataframe (.tsv, .csv or .shp)

Since a GIS interface is often the most practicale to record graph nodes and graph edges with POINTS and LINES geometries coming from shapefiles (.shp)

nodes_path <- paste0(dataDir, "/nodes.shp")
nodes.shp <- rgdal::readOGR(dsn = nodes_path, layer = "nodes", verbose = F)
nodes <- as.data.frame(nodes.shp)
edges_path <- paste0(dataDir, "/edges.shp")
edges.shp <- rgdal::readOGR(dsn = edges_path, layer = "edges", verbose = F)
edges <- as.data.frame(edges.shp)

Nodes and edges can also be recorded in tabular format: .tsv (tab-separated values) or .csv (semicolons-separated values)

nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)

The list of graph decorations is created with the list_dec() function. These graphs are igraph objects

lgrph <- list_dec(imgs, nodes, edges)
g <- lgrph[[1]]
as.character(class(g))
> [1] "igraph"

By default, the plot.igraph() function (ie, igraph::plot()) spazialisation (layout) is based on x and y columns, when exist. This is our case since we work with geometric graphs. But very different layouts exist for the same graphs (graph drawing)

par(mfrow=c(1, 2))
coords <- layout.fruchterman.reingold(lgrph[[1]])
plot(g,
     vertex.size = 30,
     vertex.frame.color="white",
     vertex.label.family = "sans",
     vertex.label.cex = .8,
)
title("graph drawing on x, y coordinates", cex.main=1, font.main= 1)
plot(g,
     layout = coords,
     vertex.size = 20 + (degree(g)*10),
     vertex.frame.color="white",
     vertex.label.family = "sans",
     vertex.label.cex = .8,
)
title(paste0("force-directed graph drawing,",
             " \n size of nodes depending on their degrees"),
      cex.main=1, font.main= 1)
mtext(lgrph[[1]]$decor, cex = 1.2, side = 3, line = -20, outer = TRUE)

 

Images

Images or drawings (ie, images, raster, grid) accepted are common formats (jpg, png, jpeg, tiff, pdf, etc.). The current images of the training dataset come from a PhD tesis, published by M. Diaz-Guardamino (Dı́az-Guardamino Uribe 2010)

imgs_path <- paste0(dataDir, "/imgs.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
knitr::kable(imgs, "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
idf site decor img
1 Cerro Muriano Cerro Muriano 1 Cerro_Muriano.Cerro_Muriano_1.jpg
2 Torrejon Rubio Torrejon Rubio 1 Torrejon_Rubio.Torrejon_Rubio_1.jpg
3 Brozas Brozas Brozas.Brozas.jpg
4 Zarza de Montanchez Zarza De Montanchez Zarza_de_Montanchez.Zarza_De_Montanchez.jpg
5 Ibahernando Ibahernando Ibahernando.Ibahernando.jpg

 

The decorations unique identifiers are the concatenation of the site name and decoration name. For example, the name of the Cerrano Muriano 1 decoration is Cerro_Muriano.Cerro_Muriano_1.jpg. For a given decoration, its image is the space of reference of the graph: nodes and edges inherit their coordinates from the image grid. Identifiers and paths to images are stored in the imgs dataframe

library(magick)
dataDir <- paste0(system.file("extdata", package = "decorr"))
imgs_path <- paste0(dataDir, "/imgs.csv")
imgs <- read.table(imgs_path, sep=";", stringsAsFactors = FALSE)
cm1 <- image_read(paste0(dataDir, "/", imgs[1,"img"]))
W <- as.character(image_info(cm1)$width)
H <- as.character(image_info(cm1)$height)
o <- "0"
image_border(image = cm1, "#808080", "2x2") %>%
  image_annotate(text = paste0(o, ", ", o),
                 size = 20,
                 gravity = "northwest") %>%
  image_annotate(text = paste0(W, ", ", o),
                 size = 20,
                 gravity = "northeast") %>%
  image_annotate(text = paste0(o, ", ", H),
                 size = 20,
                 gravity = "southwest") %>%
  image_annotate(text = paste0(W, ", ", H),
                 size = 20,
                 gravity = "southeast")
\label{fig:figs}Cerro_Muriano.Cerro_Muriano_1.jpg with the coordinates of its corners in 'px'

Cerro_Muriano.Cerro_Muriano_1.jpg with the coordinates of its corners in ‘px’

 

Nodes data

Nodes are stored in a dataframe (.csv or .tsv) or a shapefile (.shp). If the input data comes from a dataframe (.csv or .tsv), coordinates columns x and y are required

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir) 
knitr::kable(nds.df, "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
site decor id type x y
Cerro Muriano Cerro Muriano 1 1 personnage 349.8148 -298.3244
Cerro Muriano Cerro Muriano 1 2 casque 349.8148 -243.9851
Cerro Muriano Cerro Muriano 1 3 lance 238.4637 -298.3244
Cerro Muriano Cerro Muriano 1 4 bouclier 446.0222 -381.1697
Cerro Muriano Cerro Muriano 1 5 peigne 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 7 sexe_masculin 342.6884 -427.4917
Cerro Muriano Cerro Muriano 1 8 lingot_pdb 451.1489 -237.4782

While, in theory, the nodes are the exacts centroids of each GU, in a more handy manner they can be located manually near to these centroids.

column names

  • site: decoration site

  • decor: decoration name

  • id: id of the edges (a unique number)

  • type: type of the nodes

  • x, y: coordinates of the nodes

Edges data

Edges are stored in a dataframe (.csv or .tsv) or a shapefile (.shp).

edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
knitr::kable(head(edges), "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12) %>%
  gsub("\\+", "$$+$$", .)
site decor a b type
Cerro Muriano Cerro Muriano 1 1 4 =
Cerro Muriano Cerro Muriano 1 1 5 =
Cerro Muriano Cerro Muriano 1 3 5 =
Cerro Muriano Cerro Muriano 1 1 2 \[+\]
Cerro Muriano Cerro Muriano 1 1 7 \[+\]
Cerro Muriano Cerro Muriano 1 3 1 =

 

Edges geometries are calculated or recalculated from nodes geometries with a join between id nodes’ field and the a and b edges’ fields where a is the starting node of the edges (columns xa and ya) and b is the ending node of the edges (columns xa and ya).

eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir) 
knitr::kable(head(eds.df), "html") %>% 
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12) %>%
  gsub("\\+", "$$+$$", .)
site decor a b type xa ya xb yb
Cerro Muriano Cerro Muriano 1 1 4 = 349.8148 -298.3244 446.0222 -381.1697
Cerro Muriano Cerro Muriano 1 1 5 = 349.8148 -298.3244 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 3 5 = 238.4637 -298.3244 283.0041 -358.0086
Cerro Muriano Cerro Muriano 1 1 2 \[+\] 349.8148 -298.3244 349.8148 -243.9851
Cerro Muriano Cerro Muriano 1 1 7 \[+\] 349.8148 -298.3244 342.6884 -427.4917
Cerro Muriano Cerro Muriano 1 3 1 = 238.4637 -298.3244 349.8148 -298.3244

 

column names

  • site : decoration site

  • decor : decoration name

  • id : id of the edge (a unique number)

  • a : id of the first node (see, nodes)

  • b : id of the second node (see, nodes)

  • type : edges types

    • = : normal edges between contemporaneous nodes (undirected edge)

    • + : attribute edges, between contemporaneous nodes where the node b is an attribute of node a (directed edge)

    • > : diachronic edges, between non-contemporaneous nodes where the node a overlaps node b, or a is more ancient than node b (directed edge)

  • xa, ya: coordinates of the starting node, or main node, or overlapping node, or more recent node (a)

  • xb, yb: coordinates of the ending node, or attribute node, or overlapped node, , or more ancient node (b)

Edges types

As stated by the graph theory, edges can be undirected or directed. In the decorr package, by default:

The named_elements() function allow to display the textual notation of the different types of edges (-=-, -+- or ->-)

named_elements(lgrph[[1]], focus = "edges", nd.var="type")[1]      
> [1] "bouclier-=-personnage"

When there is nodes with the same nd.var, this function add the suffix # to the nd.var in order to desambiguate the nodes list. This is the case, for example, for the chariot_char-+-cheval (x2) and chariot_char-+-roue (x2) of the Zarza de Montsanchez stelae (decoration 4)

sort(named_elements(lgrph[[4]], focus = "edges", nd.var="type"))
>  [1] "bouclier-=-chariot_char"   "bouclier-=-lance"         
>  [3] "bouclier-=-personnage"     "casque-+-rivet"           
>  [5] "casque-=-epee"             "casque-=-miroir"          
>  [7] "chariot_char-+-cheval"     "chariot_char-+-cheval#"   
>  [9] "chariot_char-+-roue"       "chariot_char-+-roue#"     
> [11] "chariot_char-=-personnage" "epee-=-lance"             
> [13] "epee-=-miroir"             "lance-=-personnage"

Employed with the basic R functions for loop (lapply()), counts (table()) and order (order()), and removing the the suffix # this function can be used to count the different types of edges. Here, the most represented ones:

all.edges <- lapply(lgrph, 
                    function(x) named_elements(x, focus = "edges",
                                               nd.var="type"))
edges.list <- gsub("#", "", unlist(all.edges))
all.edges.ct <- as.data.frame(table(edges.list))
all.edges.ct <- all.edges.ct[with(all.edges.ct, order(-Freq)), ] 
knitr::kable(head(all.edges.ct), row.names = F) %>%
  kableExtra::kable_styling(full_width = FALSE, position = "center", font_size=12)
edges.list Freq
chariot_char-+-cheval 4
chariot_char-+-roue 4
bouclier-=-epee 3
bouclier-=-lance 3
bouclier-=-chariot_char 2
bouclier-=-fibule 2

normal edges

The normal edges are undirected: 1-=-2 is equal to 2-=-1, node 1 and node 2 are two different main nodes. Differents main nodes considerated as contemporaneous and close one with another, shared an edge with the value = (textual notation: -=-) for their type. By convention, these edges are called normal and displayed with a plain line.

nds.df <- read_nds(site = "Brozas", 
                   decor = "Brozas",
                   doss = dataDir)
eds.df <- read_eds(site = "Brozas",
                   decor = "Brozas",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                              edges = eds.df,
                              site = "Brozas",
                              decor = "Brozas",
                              lbl.size = 2,
                              doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = nds.df,
                                edges = eds.df,
                                site = "Brozas",
                                decor = "Brozas",
                                lbl.size = 2,
                                nd.var = 'type',
                                doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Brozas stelae (decoration 3) with only *normal* edges: all the composition seems contemporaneous

Brozas stelae (decoration 3) with only normal edges: all the composition seems contemporaneous

 

For example, the normal edge between the nodes 5 and 6 (respectively the shield and a sword) is:

attribute edges

When a node is an attribute of another, edges are identified with a + (textual notation: -+-) and displayed with a dashed line. The attribute edges are directed: 1-+-2 is not equal to 2-+-1, 1-+-2 means that node 1 is the main node and node 2 is one of its attribute node. For example, at the bottom of the Zarza de Montsanchez stelae (decoration 4), the main node 7 (chariot) is connected with four (4) nodes attributes:

  • two horses (cheval): 8 and 9

  • two wheels (roue): 10 and 11

nds.df <- read_nds(site = "Zarza de Montanchez", 
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
eds.df <- read_eds(site = "Zarza de Montanchez",
                   decor = "Zarza De Montanchez",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                              edges = eds.df,
                              site = "Zarza de Montanchez",
                              decor = "Zarza De Montanchez",
                              lbl.size = 2,
                              doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = nds.df,
                                edges = eds.df,
                                site = "Zarza de Montanchez",
                                decor = "Zarza De Montanchez",
                                lbl.size = 2,
                                nd.var = 'type',
                                doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Zarza De Montanchez stelae (decoration 4) showing *normal* and *attribute* edges

Zarza De Montanchez stelae (decoration 4) showing normal and attribute edges

 

diachronic edges

When a node overlaps another or is more recent than another, edges are identified with a > (textual notation: ->-) and displayed with a blue plain line. The diachronic edges are directed: 1->-2 is not equal to 2->-1, 1->-2 means that node 1 overlaps node 2, or node 1 is more recent than node 2. For example, the Ibahernando stelae shows a latin writing (écriture) overlapping a spear (lance) and a shield (bouclier).

nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
img.graph.id <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           lbl.size = 2,
                           doss = dataDir)
img.graph.type <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           nd.var = 'type',
                           lbl.size = 2,
                           doss = dataDir)
magick::image_append(c(magick::image_read(img.graph.id),
                       magick::image_read(img.graph.type)))
Ibahernando stelae (decoration 5) showing *normal* and *diachronic* edges

Ibahernando stelae (decoration 5) showing normal and diachronic edges

 

These overlappings, or diachronic iconographical layers, can be manage with the contemp_nds() function (see the section Contemporaneous contents)

Functions

Decoration graphs are constructed from nodes and the edges. As a RNG graph, graphs are 1-component: each decoration graph covers all the GUs of the decoration. The functions of the decorr package provide basic requirements to manage these nodes data (.csv, .tsv or .shp) and edges data (.csv, .tsv or .shp) to create graphs, to plot and to compare them, to select contemporaneous GUs compositions

cat(ls("package:decorr"), sep="\n")
> contemp_nds
> eds_compar
> labels_shadow
> list_compar
> list_dec
> named_elements
> nds_compar
> plot_compar
> plot_dec_grph
> read_eds
> read_nds
> same_elements

Plot

The graphical functions, plot_dec_grph() and plot_compar() allow different choices for the color and the size of the nodes, edges or labels. For example, nodes and edges of Cerro Muriano 1, and the field type used for the label can be selected instead of the default id field (identifier of the node)

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Cerro Muriano",
                           decor = "Cerro Muriano 1",
                           nd.var = 'type',
                           lbl.size = 1.8,
                           doss = dataDir)
magick::image_read(img.graph)
Cerro Muriano 1 stelae (decoration 1) with the type of each GU

Cerro Muriano 1 stelae (decoration 1) with the type of each GU

 

A new field, long_cm is added to Cerro Muriano 1 nodes and the graph is replot on this field instead of the type field, with brown colors and label bigger sizes

nds.df <- read_nds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
nds.df$long_cm <- c(47, 9, 47, 18, 7, 3, 13)
eds.df <- read_eds(site = "Cerro Muriano", decor = "Cerro Muriano 1", doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Cerro Muriano",
                           decor = "Cerro Muriano 1",
                           nd.var = 'long_cm',
                           nd.color = "brown",
                           lbl.color = "brown",
                           ed.color = "brown",
                           lbl.size=2.5,
                           doss = dataDir)
magick::image_read(img.graph)
Cerro Muriano 1 stelae (decoration 1) with the maximum length (in cm) of each GU

Cerro Muriano 1 stelae (decoration 1) with the maximum length (in cm) of each GU

 

Compare

Between all the graphs, or between a pair of graphs, elements of the graphs (nodes and edges) can be compared with the same_elements() and plot_compar() functions

  • same_elements() function permits to count each common elements between n graphs

  • plot_compar() function shows a graphical output for these coomon elements

By default, between a pairwise of decorations, common nodes and edges are displayed in red, but their colors – and other graphical parameters – can be modified. When not all GUs are contemporaneous one with another, the non-contemporaneous ones can be removed with the contemp_nds() function.

Nodes comparisons

A classic study in archaeological research is to count the common nodes between decoration pairwises. This can be done with the same_elements() function with a node focus (focus = "nodes"), and considering for example their type.

imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
df.same_nodes <- same_elements(lgrph,
                               focus = "nodes",
                               nd.var = "type")
diag(df.same_nodes) <- cell_spec(diag(df.same_nodes),
                                 font_size = 9)
knitr::kable(df.same_nodes, row.names = TRUE, escape = F, table.attr = "style='width:30%;'",
             caption = "count of common nodes between decorations") %>%
  column_spec(1, bold=TRUE) %>%
  kableExtra::kable_styling(position = "center", font_size = 12)
count of common nodes between decorations
1 2 3 4 5
1 7 2 3 4 2
2 2 12 5 9 3
3 3 5 6 4 3
4 4 9 4 12 3
5 2 3 3 3 4

 

The results of same_elements() is a symetrical dataframe where row names and column headers are the identifiers of the decorations:

  • cells of the dataframe show the total number of common nodes by decoration pairwises

  • the diagonal of the dataframe shows the total number of nodes or edges of a given decoration

Regarding the node type variable, the decoration 4 has twelve (12) nodes, has nine (9) common nodes with the decoration 2, and has four (4) common nodes with the decoration 3. This matrix can be used for further clustering analysis

To compare graphically the decorations 2, 3 and 4 on the type variable:

  • first: type variable is pasted to the list_compar() function
  • then: the plot is made with the plot_compar() function
dec.to.compare <- c(2, 3, 4)
imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
g.compar <- list_compar(lgrph, nd.var = "type")
nds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "nodes",
                          nd.size = c(0.5, 1.5),
                          doss = dataDir)
knitr::include_graphics(nds_compar) 

 

The function creates an image for each pairwise of stelae contained in the dec.to.compare variable (2, 3, 4), with a focus on nodes (focus = "nodes"). That is to say \(\frac{n!}{(n-2)!2!}\) pairwise comparisons, when n is the number of compared decoration. Then, to compare each pairwsise of the five (5) decorations of the training dataset, there is 10 possible comparisons.

Edges comparisons

A not so classic study in archaeological research is to count the common edges between decorations pairwise. The same_elements() function with an edge focus (focus = "edges") and considering the type of the nodes

imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges)
df.same_edges <- same_elements(lgrph, nd.var = "type", focus = "edges")
diag(df.same_edges) <- cell_spec(diag(df.same_edges),
                                 font_size = 9)
knitr::kable(df.same_edges, row.names = TRUE, escape = F, table.attr = "style='width:30%;'",
             caption = "count of common edges between decorations") %>%
  column_spec(1, bold=TRUE) %>%
  kableExtra::kable_styling(position = "center", font_size = 12)
count of common edges between decorations
1 2 3 4 5
1 8 0 1 2 0
2 0 15 3 7 1
3 1 3 10 1 2
4 2 7 1 14 1
5 0 1 2 1 4

 

In this dataframe:

  • cells show the total number of common edges by decorations

  • the diagonal of the dataframe shows is the total number of a given decoration

Here, the decoration 2 has fiftheen (15) edges and shares three (3) common edges with the decoration 3. To show them, and the decoration 4, we use the list_compar() function on the same variable (type) and the plot_compar() function with an edge focus (focus = "edges").

dec.to.compare <- c(2, 3, 4)
imgs_path <- paste0(dataDir, "/imgs.tsv")
nodes_path <- paste0(dataDir, "/nodes.tsv")
edges_path <- paste0(dataDir, "/edges.tsv")
imgs <- read.table(imgs_path, sep="\t", stringsAsFactors = FALSE)
nodes <- read.table(nodes_path, sep="\t", stringsAsFactors = FALSE)
edges <- read.table(edges_path, sep="\t", stringsAsFactors = FALSE)
lgrph <- list_dec(imgs, nodes, edges) # call function
g.compar <- list_compar(lgrph, nd.var = "type")
eds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "edges",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
# open image
knitr::include_graphics(eds_compar) 

This matrix can be used for further clustering analysis

Contemporaneous elements

It occurs that some nodes are non-contemporaneous one with the others, like for the Ibahernando stelae. This stelae have been found reused as a funerary steale during Roman times with latin insciption “Alloquiu protaeidi.f hece. stitus”: Alluquio, son of Protacido, lies here (Basch 1966).

GIS view. The Ibahernando stelae (decoration 5)

GIS view. The Ibahernando stelae (decoration 5)

The writing (ecriture, node 1) has been carved over a spear (lance, node 2) and overlaps partially a V-notched shield (bouclier, node 3). The edges between node 1 and node 2, and the edge between node 1 and node 3, are diachronic edges

nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
img.graph <- plot_dec_grph(nodes = nds.df,
                           edges = eds.df,
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           lbl.size = 2,
                           doss = dataDir)
magick::image_read(img.graph)
Ibahernando stelae (decoration 5) with only *normal* edges, node 1  overlaps node 2 and node 3

Ibahernando stelae (decoration 5) with only normal edges, node 1 overlaps node 2 and node 3

 

In this case, the non-contemporaneous layers of decoration, both for nodes and edges, should be removed before the comparison process. To do so, the original graph (1-component) will be splitted into different sub-graphs (n-component) by selecting > edges (see diachronic edges). The studied graph component will be retrieved with the component membership of a selected node in the contemp_nds() function parameters.

To study only the Late Bronze Age iconographic layer of the Ibahernando steale, we can choose the Late Bronze Age node 4, the figure of sword (epee) dated to the middle and final stages of Late Bronze Age (ca 1250-950 BC). This node is supposed to be contemporaneous to node 2 and node 3 (see normal edges)

selected.nd <- 4
nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
l_dec_df <- contemp_nds(nds.df, eds.df, selected.nd)
Ibahernando <- plot_dec_grph(nodes = nds.df,
                             edges = eds.df,
                             site = "Ibahernando",
                             decor = "Ibahernando",
                             nd.var = "type",
                             lbl.color = "brown",
                             lbl.size = 2.2,
                             doss = dataDir)
Ibahernando.img <- magick::image_read(Ibahernando)
Ibahernando.contemp <- plot_dec_grph(nodes = l_dec_df[[1]],
                                     edges = l_dec_df[[2]],
                                     site = "Ibahernando",
                                     decor = "Ibahernando",
                                     nd.var = "type",
                                     lbl.color = "brown",
                                     lbl.size = 2.2,
                                     doss = dataDir)
Ibahernando.contemp.img <- magick::image_read(Ibahernando.contemp)
magick::image_append(c(Ibahernando.img, Ibahernando.contemp.img))
Ibahernando stelae before and after the selection of node 4 (sword) graph component

Ibahernando stelae before and after the selection of node 4 (sword) graph component

 

At the opposite, epigraphists will study only the iconographic layer of the latin writing. By selecting the node 1 (ecriture), only the component of this node will be selected

selected.nd <- 1
nds.df <- read_nds(site = "Ibahernando", 
                   decor = "Ibahernando",
                   doss = dataDir)
eds.df <- read_eds(site = "Ibahernando",
                   decor = "Ibahernando",
                   doss = dataDir)
l_dec_df <- contemp_nds(nds.df, eds.df, selected.nd)
img.graph <- plot_dec_grph(nodes = l_dec_df[[1]],
                           edges = l_dec_df[[2]],
                           site = "Ibahernando",
                           decor = "Ibahernando",
                           nd.var = "type",
                           lbl.size = 2,
                           lbl.color = "brown",
                           doss = dataDir)
magick::image_read(img.graph)
Ibahernando stelae after the selection of node 1 (writing) graph component

Ibahernando stelae after the selection of node 1 (writing) graph component

 

Summary

Plasticity of graph theory and facilities coming from the GIS database make decorr package usefull to manage, plot and compare large series of iconographical contents.

Classify decorations

Beside graphical functions permitting to highlight common elements (nodes and edges) between decorations, the package allows to prepare data for unsupervised classification, like Hierarchical clustering with the dist() and hclust()

par(mfrow=c(1, 2))
df.same_edges <- same_elements(lgrph, "type", "edges")
df.same_nodes<- same_elements(lgrph, "type", "nodes")
dist.nodes <- dist(as.matrix(df.same_nodes), method = "euclidean")
dist.edges <- dist(as.matrix(df.same_edges), method = "euclidean")
hc.nds <- hclust(dist.nodes, method = "ward.D")
hc.eds <- hclust(dist.edges, method = "ward.D") 
plot(hc.nds, main = "common nodes", cex = .8)
plot(hc.eds, main = "common edges", cex = .8)

 

Decorations clustering on commons nodes and decorations clustering on commons edges can be compared one to another

suppressPackageStartupMessages(library(dendextend))
suppressPackageStartupMessages(library(dplyr))
par(mfrow=c(1, 2))
dend.nds <- as.dendrogram (hc.nds)
dend.eds <- as.dendrogram (hc.eds)
dendlist(dend.nds, dend.eds) %>%
  untangle(method = "step1side") %>% 
  tanglegram(columns_width = c(6, 1, 6),
             main_left = "common nodes",
             main_right = "common edges",
             lab.cex = 1.3,
             cex_main = 1.5,
             highlight_branches_lwd = F) 

 

In both clusterings, Brozas stelae (decoration 3) and Ibahernando stelae (decoration 5) are the ones having the most important proximities (ie the lesser euclidian distance)

dec.to.compare <- c(3, 5)
g.compar <- list_compar(lgrph, nd.var = "type")
nds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "nodes",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
eds_compar <- plot_compar(listg = g.compar, 
                          graph2 = dec.to.compare,
                          focus = "edges",
                          nd.size = c(0.5, 1.7),
                          doss = dataDir)
knitr::include_graphics(nds_compar)

knitr::include_graphics(eds_compar) 

Here, the comparisons have only been done on the different type of (ie, graphical units, GUs) with the variable type (nd.var = "type"). But, if a new column is added to the node dataframe or shapefile, the study can also integrate the technique (nd.var = "technique") or any other controled vocabularies. For example, two GUs displayed on the Brozas steale have been made with incisions (g_inc): the fibula (fibula de codo tipo Huelva, ca 1050-950 BC) and the comb

GIS view. The Brozas stelae (decoration 1)

GIS view. The Brozas stelae (decoration 1)

Nodes tree

Graph theory permit to construct tree structures for controled vocabulary (eg. the different types of GUs). These structures allow generalization processes (up to the parent level) and specification processes (down to the children level). For example, a sword and a spear belongs both to the weapons group (sub-group offensive weapons), a shield belongs to the weapons group (sub-group defensive weapons), etc.

par(mar=c(0,0,0,0))
g <- graph_from_literal(objects-+weapons,
                        objects-+personnal_item,
                        weapons-+offensive_weapons,
                        weapons-+defensive_weapons,
                        offensive_weapons-+spear,
                        offensive_weapons-+sword,
                        defensive_weapons-+shield,
                        defensive_weapons-+helmet,
                        personnal_item-+miror,
                        personnal_item-+comb)
layout <- layout.reingold.tilford(g)
plot(g,
     layout = layout,
     vertex.color = "white",
     vertex.frame.color = "white",
     vertex.size = 20,
     vertex.label.cex = 0.8,
     vertex.label.color = "black",
     vertex.label.family = "sans",
     edge.arrow.size = 0.5
     )

Such a formalism can be used to weight the differences between nodes, to conduct analysis with different level of precision or to overcome idiosynchratic typologies issues.

Using the diachronic edge notation, tree structure can also be used to construct relative chronology diagrams, like a Harris matrix. For example, with the data.tree package and the Ibahernado stelae

library(data.tree)
lgrph <- list_dec(imgs, nodes, edges)
edges.iba <- igraph::as_data_frame(lgrph[[5]], what="edges")
overlap.nodes <- unlist(unique(edges.iba[edges.iba$type == ">", "from"]))
overlap.nodes <- unique(as.character(overlap.nodes))
contemp.nodes <- unlist(unique(edges.iba[edges.iba$type == "=", c("from","to")]))
contemp.nodes <- unique(as.character(contemp.nodes))
df.stratig <- data.frame(over = rep(overlap.nodes, length(contemp.nodes)),
                            under = contemp.nodes)

df.stratig$pathString <- paste(lgrph[[5]]$decor,
                               df.stratig$over, 
                               df.stratig$under, 
                               sep = "/")
superpo <- as.Node(df.stratig)
print(superpo)
>     levelName
> 1 Ibahernando
> 2  °--1      
> 3      ¦--2  
> 4      ¦--3  
> 5      °--4

References

Alexander, Craig. 2008. “The Bedolina Map – an Exploratory Network Analysis.” In Layers of Perception. Proceedings of the 35th International Conference on Computer Applications and Quantitative Methods in Archaeology (Caa), Berlin, 2.-6. April 2007, edited by A. Posluschny, K. Lambers, and I. Herzog, 366–71. Koll. Vor- u. Frühgesch. https://doi.org/https://doi.org/10.11588/propylaeumdok.00000512.

Basch, Martı́n Almagro. 1966. Las Estelas Decoradas Del Suroeste Peninsular. Vol. 8. Editorial CSIC-CSIC Press.

Dı́az-Guardamino Uribe, Marta. 2010. “Las Estelas Decoradas En La Prehistoria de La Penı́nsula Ibérica.” PhD thesis, Universidad Complutense de Madrid, Servicio de Publicaciones. https://eprints.ucm.es/11070/1/T32200.pdf.

Huet, Thomas. 2018. “Geometric Graphs to Study Ceramic Decoration.” In Exploring Oceans of Data, Proceedings of the 44th Conference on Computer Applications and Quantitative Methods in Archaeology, Caa 2016, edited by Mieko Matsumoto and Espen Uleberg, 311–24. Archaeopress. https://hal.archives-ouvertes.fr/hal-02913656.

Huet, Thomas, and Craig Alexander. 2015. “Méthodes Informatiques Pour L’étude Des Gravures Rupestres : Les Exemples Du Valcamonica (Italie) et Du Mont Bego (France).” In Recherches Sur L’âge Du Bronze, Nouvelles Approches et Perspectives, Actes de La Journée d’étude de L’Association Pour La Promotion Des Recherches Archéologiques Sur L’âge Du Bronze, 28 Février 2014, Saint-Germain-En-Laye, Bulletin de L’APRAB, Suppl. N° 1, edited by M. Nordez, L. Rousseau, and M. Cervel, 15–29. Nantes. https://www.researchgate.net/publication/347437308_Methodes_informatiques_pour_l'etude_des_gravures_rupestres_les_exemples_du_Valcamonica_Italie_et_du_mont_Bego_France.